use std::sync::Arc;

use axum::{
    extract::{Path, State},
    http::StatusCode,
    routing::get,
    Router,
};
use tower_http::services::ServeDir;
use tracing::{info, warn};

#[derive(Debug)]
struct HttpServeState {
    path: std::path::PathBuf,
}

pub async fn process_http_serve(path: std::path::PathBuf, port: u16) -> anyhow::Result<()> {
    let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
    info!("Serving {:?} on port {}", path, port);
    let state = HttpServeState { path: path.clone() };
    let dir_service = ServeDir::new(path)
        .append_index_html_on_directories(true)
        .precompressed_gzip()
        .precompressed_br()
        .precompressed_deflate()
        .precompressed_zstd();
    let router = Router::new()
        .route("/static/{*path}", get(file_handler))
        .fallback_service(dir_service)
        // .route_service("/tower", dir_service)
        .with_state(Arc::new(state));
    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, router).await?;

    Ok(())
}

async fn file_handler(
    State(state): State<Arc<HttpServeState>>,
    Path(path): Path<String>,
) -> (StatusCode, String) {
    let p = std::path::Path::new(&state.path).join(path.clone());
    info!(
        "Reading file {:?}, state.path: {:?}, path: {}",
        p, state.path, path
    );
    if !p.exists() {
        return (
            StatusCode::NOT_FOUND,
            format!("File {} not found", p.display()),
        );
    } else {
        match tokio::fs::read_to_string(p).await {
            Ok(content) => {
                info!("Read {} bytes", content.len());
                return (StatusCode::OK, format!("{}", content));
            }
            Err(err) => {
                warn!("Error reading file: {:?}", err);
                return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string());
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn file_handler_test() {
        let state = Arc::new(HttpServeState {
            path: std::path::PathBuf::from("."),
        });

        let (status, content) = file_handler(State(state), Path("Cargo.toml".to_string())).await;
        println!("content: {}", content);
        assert_eq!(status, StatusCode::OK);
    }
}
